# 打开开发者模式
# 关于手机>>>版本信息>>>点击版本号8次
# 系统设置>>>开发者选项+USB调试+显示当前触摸数据
# 本程序适用分辨率1080*2280或者1080*2400

# 系统应用
import os
import time
import threading
import subprocess
import re
import traceback
import logging
import random
import sys
import schedule
import cv2
import requests

sys.path.append("D:/Myprog")

if not os.path.exists("D:/Myprog/Android/Image"):
    os.makedirs("D:/Myprog/Android/Image")

logging.basicConfig(
    format="[%(asctime)s] %(filename)s:%(lineno)d %(funcName)s() [%(levelname)s]: %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
    level=logging.INFO,
)
logger = logging.getLogger(__name__)

# 图像定位
try:
    import aircv as av
except ModuleNotFoundError:
    logger.error(
        "Please: pip install opencv-python -i https://mirror.baidu.com/pypi/simple/"
    )
    os.system("pip install opencv-python -i https://mirror.baidu.com/pypi/simple/")

# 图片处理
try:
    from PIL import Image
    from PIL import ImageDraw
    from PIL import ImageFont
    from PIL import ImageFile

    ImageFile.LOAD_TRUNCATED_IMAGES = True
except ModuleNotFoundError:
    logger.error("Please: pip install pillow")
    os.system("python -m pip install pillow")

# 输出颜色
try:
    from Panda.Core.Prt import prt
    from Panda.Core.Email import email
except ModuleNotFoundError:
    logger.error("请查阅是否存在Panda库,若无可以将prt修改为print")  # 15806630684


class Device:
    """用于集成adb -s {Device.device}的各项操作"""

    device = None
    always = True

    def __init__(self):
        pass

    @staticmethod
    def usb():
        os.system("adb devices")
        Device.device = "OFNBJVBYQ8CMMJIF"  # 'OFNBJVBYQ8CMMJIF'  # 'EMPFGYFIAQH6VKLZ'

    @staticmethod
    def tcpip():
        os.system(f"adb tcpip 5555")

    @staticmethod
    def wifi(ip="192.168.0.105"):  # 进行远程调试
        """ip:192.168.0.1xx"""
        Device.tcpip()
        subcmd(f"adb connect {ip}", stdout=True)
        Device.device = f"{ip}:5555"  # '192.168.0.110:5555'

    @classmethod
    def click(cls, x, y, wait=0.01, times=1):
        for t in range(times):
            os.system(f"adb -s {Device.device} shell input tap {x} {y}")
            time.sleep(wait)

    @staticmethod
    def start(activity="", is_wait=1):  # 获取程序入口
        """
        启动程序入口activity！
        activity为空，表示获取当前活动程序入口
        """
        if activity and is_wait:
            subcmd(f"adb -s {Device.device} shell am start -W {activity}")
        elif activity and not is_wait:
            subcmd(f"adb -s {Device.device} shell am start {activity}")
        else:
            subcmd(
                f'adb -s {Device.device} shell "dumpsys window | grep mCurrentFocus"',
                True,
            )

    @property
    def info(self):
        info = {
            "serial": subcmd(f"adb -s {Device.device} devices")
            .split("\n")[1]
            .strip()
            .replace("device", "")
            .strip(),
            "size": subcmd(f"adb -s {Device.device} shell wm size")
            .split(":")[1]
            .strip()
            .split("x"),
        }
        return info

    @staticmethod
    def screen(
            img_name="screen.png", pc_path="D:/Myprog/Android/Image/", ph_path="/sdcard/"
    ):
        """
        pull:表示手机ph拉至电脑pc
        push:表示电脑pc推至手机ph
        """
        # 截屏到手机
        subcmd(f"adb -s {Device.device} shell screencap {ph_path}{img_name}")
        Device.pull(os.path.join(ph_path, img_name), pc_path)

    @staticmethod
    def clear(times=3):
        os.system(f"adb -s {Device.device} shell input keyevent 224")  # 亮屏，黑屏是223
        Device.swipe()
        Device.keyevent("187")  # 切屏按键 等同于：tap(270, 2275)
        for t in range(times):  # 上划清除缓存
            Device.swipe()
        Device.keyevent("HOME")

    @staticmethod
    def pull(ph_file, pc_path=os.getcwd(), rename=None):
        subcmd(f"adb -s {Device.device} pull {ph_file} {pc_path}")
        if rename:
            scr = os.path.join(pc_path, os.path.split(ph_file)[1])
            new = os.path.join(pc_path, rename)
            os.replace(scr, new)

    @staticmethod
    def push(pc_file, ph_path="/sdcard/"):
        subcmd(f"adb -s {Device.device} push {pc_file} {ph_path}", True)

    @staticmethod
    def find(screen_name="dingding", screen_find="dk", is_tap=1, confidence=0.80):
        source = av.imread(f'{"./Image/"}{screen_name}.png')
        search = av.imread(f'{"./Image/"}{screen_find}.png')
        result = av.find_template(source, search, confidence)
        if result:
            prt(
                f'{time_str()}From {screen_name} find {screen_find}! tap{result["result"]} {result["confidence"]}',
                zi="lan",
            )
            if is_tap:
                Device.click(result["result"][0], result["result"][1], 1)
            return result["result"][0], result["result"][1]
        else:
            prt(
                f"{time_str()}From {screen_name} not find {screen_find}! {result}",
                zi="红",
            )
            return False

    @staticmethod
    def keyevent(key: str):
        if key.isdigit():
            os.system(f"adb -s {Device.device} shell input keyevent {key}")
        else:
            os.system(f"adb -s {Device.device} shell input keyevent KEYCODE_{key}")

    @classmethod
    def phone(cls, key: str):
        keys = {
            "1": (200, 1100),
            "2": (550, 1100),
            "3": (850, 1100),
            "4": (200, 1350),
            "5": (550, 1350),
            "6": (850, 1350),
            "7": (200, 1550),
            "8": (550, 1550),
            "9": (850, 1550),
            "0": (550, 1750),
        }
        if key.isdigit():
            for k in key:
                x, y = keys[k]
                cls.click(x, y)
                time.sleep(0.3)
        cls.click(550, 2000)

    @staticmethod
    def swipe(x1=530, y1=1500, x2=530, y2=500, direct="up", delay=500, times=1):
        for i in range(times):
            if direct == "down":
                os.system(
                    f"adb -s {Device.device} shell input swipe {x2} {y2} {x1} {y1} {delay}"
                )
            elif direct == "left":
                os.system(
                    f"adb -s {Device.device} shell input swipe 1000 {y1} {x1} {y1} {delay}"
                )
            elif direct == "right":
                os.system(
                    f"adb -s {Device.device} shell input swipe {x1} {y1} 1000 {y1} {delay}"
                )
            else:
                os.system(
                    f"adb -s {Device.device} shell input swipe {x1} {y1} {x2} {y2} {delay}"
                )

    @staticmethod
    def putin(msg: str):
        Device.keyboard("adb")
        if " " in msg:
            msgs = msg.split(" ")
            for m in msgs:
                subcmd(
                    f'adb -s {Device.device} shell am broadcast -a ADB_INPUT_TEXT --es msg "{m}"'
                )
                subcmd(f'adb -s {Device.device} shell input text "%s"')
        else:
            subcmd(
                f'adb -s {Device.device} shell am broadcast -a ADB_INPUT_TEXT --es msg "{msg}"'
            )

    @staticmethod
    def keyboard(name="adb"):
        board = {
            "sougou": "com.sohu.inputmethod.sogouoem/.SogouIME",
            "adb": "com.android.adbkeyboard/.AdbIME",
        }
        if name in board:
            subcmd(f"adb -s {Device.device} shell ime set {board[name]}")
        else:
            subcmd(f"adb -s {Device.device} shell ime  list -s")
        time.sleep(1)

    @staticmethod
    def app(name):
        if name == "wechat":
            Device.start("com.tencent.mm/com.tencent.mm.ui.LauncherUI", is_wait=1)
        elif name == "dd":
            Device.start(
                "com.alibaba.android.rimet/com.alibaba.android.rimet.biz.LaunchHomeActivity",
                is_wait=1,
            )
        elif name == "ykt":
            Device.start(
                "com.zhkj.dzkxt.myapplication/com.zhkj.dzkxt.lg.activityview.MainActivity",
                is_wait=1,
            )

    @staticmethod
    def toweb():
        Device.screen("screen.png")
        merge()
        # time.sleep(1)
        return os.path.realpath("./Image/html.png")

    @staticmethod
    def states():
        out = subcmd(f"adb -s {Device.device} shell dumpsys window policy")
        pat1 = re.compile("SCREEN_STATE_([A-Z]+)")
        pat2 = re.compile(" showing=(.*)")
        screen = pat1.findall(out, re.S)[0]
        isshow = pat2.findall(out, re.S)[0]
        screen_flag = True if screen.strip() == "OFF" else False
        isshow_flag = True if isshow.strip() == "true" else False
        return screen_flag, isshow_flag


def time_str():
    return time.strftime("%Y-%m-%d %X ", time.localtime())


def subcmd(command, stdout=False):
    output = subprocess.check_output(command, shell=True).decode()  # 只获取output
    logger.info(output) if stdout else None
    return output


def learn(do=False):  # todo
    if time.localtime().tm_wday != 7 or do:
        Device.clear()
        Device.start(
            "cn.xuexi.android/com.alibaba.android.rimet.biz.SplashActivity", is_wait=1
        )
        time.sleep(5)
        Device.click(800, 150, 3)  # 积分
        Device.click(900, 1500, 3)  # 阅读
        Device.click(200, 1300, 3)  # 第一篇
        for _ in range(30):
            Device.swipe(direct="up")
            time.sleep(random.randint(10, 15))
        Device.keyevent("4")
        Device.screen()
        mark()


def NetworkError():
    url = "http://www.baidu.com"
    try:
        requests.get(url, timeout=3)
    except requests.ConnectionError:
        return "Network connect failed!"
    return


def dk_dd():
    if time.localtime().tm_wday + 1 < 6 and Device.always:
        rand_time = random.randint(60, 300)
        flag = True
        logger.info(rand_time)
        while rand_time:
            print(f"DK after {rand_time}s!", end="\r", flush=True)
            time.sleep(1)
            rand_time -= 1
    elif Device.always:
        flag = True
    else:
        flag = False
    logger.warning(Device.always)
    if flag:
        Device.clear()
        Device.start(
            "com.alibaba.android.rimet/com.alibaba.android.rimet.biz.LaunchHomeActivity",
            is_wait=1,
        )
        time.sleep(8)
        Device.click(530, 2050, 8)  # 工作台
        Device.click(140, 1300, 8)  # 考勤打卡按钮-等待位置缓冲
        Device.click(530, 1300, 1)  # 打卡
        Device.click(800, 1300, 1)  # 确认继续打卡
        Device.click(680, 2050, 3)  # 统计
        Device.swipe(times=1)
        Device.screen()
        wechat()
        if not NetworkError():
            email(
                mail_to="646318717@qq.com",
                mail_subject="钉钉考勤",
                mail_text=time_str(),
                att_path=r"D:\Myprog\Android\Image\screen.png",
            )
        else:
            logger.error("Network connect failed!")


def wechat(to_who="小熊"):
    Device.screen()
    Device.clear()
    Device.start("com.tencent.mm/com.tencent.mm.ui.LauncherUI", is_wait=1)
    time.sleep(2)
    Device.click(900, 160, 2)  # 右上角加号
    Device.putin(to_who)
    time.sleep(2)
    Device.click(400, 435, 2)  # 选择聊天对象
    Device.keyboard(f"adb")
    Device.click(1000, 2050, 2)  # 右下角加号
    Device.click(175, 1580, 2)  # 图片
    Device.click(200, 300, 2)  # 首个图片
    Device.click(920, 2050, 2)  # 确认发送


def mark(img_name="screen.png", save_name="html.png", path="D:/Myprog/Android/Image/"):
    """远程图像可视化：坐标定位图片标记"""
    im = Image.open(os.path.join(path, img_name))
    draw = ImageDraw.Draw(im)
    font = ImageFont.truetype(r"C:\Windows\Fonts\simkai.ttf", 30)
    x_max, y_max = im.size
    for i in range(11):
        draw.line([i * 100, 0, i * 100, y_max], fill="red")
        draw.text((i * 100 - 15, 20), text=str(i * 100), fill="red", font=font)
        draw.text((i * 100 - 25, 1100 - 15), text=str(i * 100), fill="red", font=font)
        draw.text((i * 100 - 25, 2200 - 15), text=str(i * 100), fill="red", font=font)
    for i in range(23):
        draw.line([0, i * 100, 1080, i * 100], fill="red")
        draw.text((20, i * 100 - 15), text=str(i * 100), fill="red", font=font)
    im.save(os.path.join(path, save_name))


def lines_image(img_name="screen.png", path="D:/Myprog/Android/Image/"):
    # 打开现有图片
    image = Image.open(os.path.join(path, img_name))

    # 获取图片的宽度和高度
    width, height = image.size
    # 创建一个空白图片，与现有图片具有相同的大小
    blank_image = Image.new("RGB", (width, height), "white")
    # 保存空白图片
    blank_image.save(os.path.join(path, "_blank.png"))
    mark("_blank.png", save_name="lines.png")
    if os.path.exists(os.path.join(path, "_blank.png")):
        os.remove(os.path.join(path, "_blank.png"))


def merge(img_name="lines.png", save_name="html.png", path="D:/Myprog/Android/Image/"):
    if not os.path.exists(os.path.join(path, "lines.png")):
        lines_image()
    # 读取两个图片
    # lines_image()
    img1 = cv2.imread(os.path.join(path, img_name))
    img2 = cv2.imread(os.path.join(path, "screen.png"))

    # 确保两个图片大小相同
    if img1.shape != img2.shape:
        print("两个图片大小不一致，请注意！")
        img2 = cv2.resize(img2, (img1.shape[1], img1.shape[0]))

    # 计算透明度
    alpha = 0.4  # 透明度，0表示完全透明，1表示完全不透明
    beta = 1 - alpha

    # 应用透明叠加层
    result = cv2.addWeighted(img1, alpha, img2, beta, 0)
    result_pil = Image.fromarray(cv2.cvtColor(result, cv2.COLOR_BGR2RGB))

    # 显示结果图片
    result_pil.save(os.path.join(path, save_name), optimize=True, quality=10)


def phcmd(command):
    """集成的各项操作指令"""
    try:
        os.system(f"adb -s {Device.device} shell input keyevent 224")
        direct = {"down", "up", "left", "right"}
        if command in direct:
            Device.swipe(direct=command)
        elif command == "re":
            pass
        elif command == "back":
            Device.keyevent("4")
        elif command == "home":
            Device.keyevent("3")
        elif command == "always":
            Device.always = (Device.always + 1) % 2
            logger.warning(Device.always)
        elif str(command).startswith("call"):
            Device.keyevent("3")
            Device.keyevent("5")
            time.sleep(2)
            Device.phone(str(command).removeprefix("call").strip())
        elif command == "menu":
            Device.keyevent("82")
        elif command == "clear":
            Device.clear()
        elif command == "dk":
            dk_dd()
        elif "++" == command[:2]:
            Device.putin(command[2:])
        elif "+" == command[:1]:
            Device.click(820, 2050, 1)
            Device.putin(command[1:])
            Device.click(980, 2050, 1)
            Device.click(1000, 1200, 1)
        elif command in ["wechat", "dd", "ykt"]:
            Device.app(name=command)
        else:
            x = command.split(" ")[0] if " " in command else 1
            y = command.split(" ")[1] if " " in command else 1
            y = y if y else 1
            logger.info(f"Position ({x}, {y}) ")
            if int(x) * int(y) == 0:
                return 0
            Device.click(x, y, 1)
            logger.info("is clicked!")
        return 1
    except Exception as ex:
        logger.error(f"{traceback.format_exc()}\n{ex}")
        return 1


def main():
    """主要打卡计划端口"""
    times = ["07:20", "11:45", "13:00", "18:00"]
    print(f"打卡计划已经启动！时间点：{'，'.join(times)}！")
    for t in times:
        schedule.every().day.at(t).do(dk_dd)  # 钉钉可自动下班打卡
    while True:
        schedule.run_pending()


def para(debug=1):
    """同main并行的进程，用于获取指令"""
    time.sleep(5)
    while debug:
        state = Device.states()
        screen_state, show_state = state[0], state[1]

        if screen_state:
            os.system(f"adb -s {Device.device} shell input keyevent 224")
            time.sleep(0.2)
        if show_state:
            Device.swipe()

        Device.screen("screen.png")
        merge()
        command = input(
            "指令：x y|up left|re|back|home|menu|call|clear|+msg\n>>>|"
        ).strip()
        flag = phcmd(command)
        if flag:
            continue
        else:
            break


if __name__ == "__main__":
    """多线程下允许计划继续执行的同时，支持远程电脑快捷操作手机"""

    d = Device()
    d.usb()
    d.start()
    # dk_dd(True)
    do1 = threading.Thread(target=para, args=(1,), daemon=True)
    do2 = threading.Thread(target=main)
    do1.start()
    do2.start()
